/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
 *
 */

import path from 'path';

import { isSubPath } from '@ohos/hvigor-common';
import fse from 'fs-extra';
import { validRange } from 'semver';

import { CommonConst, ParameterizationConst } from '../const/common-const.js';
import { OhosLogger } from '../utils/log/ohos-logger.js';
import { isLocalDependency } from '../utils/npm-utils.js';
import { isValidateTag, pathParamsOpt } from './dependency-tag-validate.js';

const log: OhosLogger = OhosLogger.getLogger('ParameterFileLog');

const PARAMETER_KEY_REG = /(?=^(@(?![0-9\-_])[a-z0-9\-_]+(?<![-_])\/)?(?![0-9\-_.])[a-z0-9\-_.]+(?<![-_.])$).{1,128}$/i;

interface NestedObject {
  [key: string]: any;
}

export interface ParameterizationMatchParams {
  /** 假如在 oh-package.json 中填写了 @param:key1.key2.key3，则该参数为 ['key1','key2','key3'] */
  keys: string[];

  /** parameterFile.json5 对象 */
  parameterFileJson: Record<string, unknown>;

  /** oh-package.json5 和 parameterFile.json5 的文件路径 */
  pathParamsObj: pathParamsOpt;

  /**
   * 当读取 parameterFile 中的属性值是本地依赖而不是版本号时，需要去获取其他模块的 oh-package.json 信息
   * 假如不传这个参数则不会替换参数化的本地依赖
   */
  ohPackageInfoGetter?: OhPackageInfoGetter;

  /**
   * 假如在 oh-package.json5 中填写了 har1: '@param:dep.har1'，则该参数为 har1
   * 用于检查依赖名与被依赖模块的name是否相同，即 har1 的 oh-package.json5 name 必须也是 har1
   * 也用于检查 tag 不可用于 version 字段，详看错误码 HE10003
   */
  dependencyName?: string;
}

export interface ParameterizationMatchResult {
  value: string;
  localDepArr?: string[];
}

/**
 * 当读取 parameterFile 中的属性值是模块目录时，需要去获取其他模块的 oh-package.json 信息
 */
export type OhPackageInfoGetter = (ohPackageJsonAbsolutePath: string) => {
  version: string;
  name: string;
};

/**
 * 这段匹配逻辑来自 ohpm
 * @param keys
 * @param parameterJson
 */
function matchValueWithLongestPrefixStrategy(keys: string[], parameterJson: Record<string, any> | string): string {
  let curValue: Record<string, any> | string = '';
  for (let j = keys.length; j > 0; j--) {
    const key = keys.slice(0, j).join('.');
    curValue = getValueFromJson(key, curValue, parameterJson);
    if (typeof curValue === 'string') {
      return curValue;
    }

    if (!curValue) {
      continue;
    }

    if (typeof curValue === 'object' && j !== keys.length) {
      curValue = matchValueWithLongestPrefixStrategy(keys.slice(j, keys.length), curValue);
      if (curValue) {
        return curValue;
      }
    }
  }
  return '';
}

/**
 * 假如在 oh-package.json5 中填写了 @param:key1.key2.key3，该函数根据 key1.key2.key3 在 parameterFile.json5 中查找相应的属性值
 * 注意 . 可能会是键的一部分，在 parameterFile.json5 可以有 'key1.key2.key3': '1.0.0'，优先级详看单测 dependenct-param-validate.unittest.ts
 * @param params
 */
export function matchValueWithParameterizationKey({
  keys,
  parameterFileJson,
  pathParamsObj,
  ohPackageInfoGetter,
  dependencyName,
}: ParameterizationMatchParams): ParameterizationMatchResult {
  const value = matchValueWithLongestPrefixStrategy(keys, parameterFileJson);

  // 代码走到该位置时证明已经进行parameterFile文件的替换，pathParamsObj.parameterFilePath必定存在
  if (!value && pathParamsObj.parameterFilePath) {
    log.printErrorExit('INVALID_PARAMETER_KEYS', [keys.join('.')], [[pathParamsObj.parameterFilePath]]);
  }
  return processParameterizationValue({
    value,
    key: keys.join('.'),
    parameterFileJson,
    pathParamsObj: pathParamsObj as Required<pathParamsOpt>,
    ohPackageInfoGetter,
    dependencyName,
  });
}

/**
 * 从json中获取value值
 *
 * @param key
 * @param targetJson  当前使用的json对象
 * @param rootJson  parameterFile文件的json对象
 */
function getValueFromJson(key: string, targetJson: any, rootJson: any): any {
  if (targetJson) {
    targetJson = getObjectValueByKey(key, targetJson);
  } else {
    targetJson = getObjectValueByKey(key, rootJson);
  }
  return targetJson;
}

/**
 * 使用key在传过来的
 *
 * @param key
 * @param parameterJson
 */
function getObjectValueByKey(key: string, parameterJson: any): any {
  if (typeof parameterJson === 'string') {
    return parameterJson;
  }
  return parameterJson[key as keyof typeof parameterJson];
}

/**
 * 校验parameterFile的key值是否符合规格
 *
 * @param parameterFileObj
 */
export function validataParameterKeys(parameterFileObj: NestedObject): void {
  Object.keys(parameterFileObj).forEach((key) => {
    if (!PARAMETER_KEY_REG.test(key)) {
      log.printErrorExit('INCORRECT_PARAMETER_KEYS_FORMAT', [key]);
    }
    if (typeof parameterFileObj[key] === 'object' && parameterFileObj[key] !== null) {
      validataParameterKeys(parameterFileObj[key]);
    }
  });
}

interface ProcessParameterizationValueParams {
  value: string;
  key: string;
  pathParamsObj: Required<pathParamsOpt>;
  parameterFileJson: Record<string, unknown>;
  ohPackageInfoGetter?: OhPackageInfoGetter;
  dependencyName?: string;
}

/**
 * 已经获取到value值，对value值进行进一步校验并获取value值
 * @param params
 */
function processParameterizationValue(params: ProcessParameterizationValueParams): ParameterizationMatchResult {
  const { value, key, pathParamsObj, dependencyName } = params;

  if (value === 'latest' || value === '*') {
    return { value };
  }

  /**
   * 在 parameterFile.json5 中找到的值是本地依赖
   */
  if (isLocalDependency(value)) {
    if (params.ohPackageInfoGetter && params.dependencyName && params.pathParamsObj?.parameterFilePath) {
      return handleParameterizationLocalDependencies(params as Required<ProcessParameterizationValueParams>)!;
    } else {
      // 假如不传 ohPackageInfoGetter 说明是在 ohos-module-task 构造函数中的第一次替换，此时遇到参数化的本地依赖保持原值不作处理
      return {
        value: `${ParameterizationConst.PREFIX}${key}`,
        localDepArr: [],
      };
    }
  }

  if (
    isValidateTag({
      key,
      value,
      parameterFilePath: pathParamsObj.parameterFilePath,
      ohPackageFilePath: pathParamsObj.ohPackageFilePath,
      dependencyName,
    })
  ) {
    return { value };
  }

  if (!validRange(value, { loose: true, includePrerelease: true })) {
    log.printErrorExit('INVALID_PARAMETER_VALUES', [value]);
  }

  return { value };
}

/**
 * 已经拿到了 parameterFile 里面的属性值，需要做额外处理或者校验
 * @param params
 */
function handleParameterizationLocalDependencies(params: Required<ProcessParameterizationValueParams>): ParameterizationMatchResult | undefined {
  const { value, pathParamsObj, ohPackageInfoGetter, dependencyName, parameterFileJson } = params;

  /** parameterFile 填的本地依赖路径可能会有 file: 前缀，或者是没有前缀只有路径 */
  const depRawPath = value.startsWith(CommonConst.LOCAL_DEPENDENCY_PREFIX) ? value.slice(CommonConst.LOCAL_DEPENDENCY_PREFIX.length) : value;

  const absoluteDepPath = path.resolve(path.dirname(pathParamsObj.parameterFilePath), depRawPath);

  /** parameterFile 中填写的路径是大小写敏感的，校验路径是否存在时还需要考虑大小写问题 */
  let isPathExist = false;
  try {
    // 在 Windows 上 existsSync 不是大小写敏感的，需要用 realpathSync 保证大小写不敏感的路径不能通过校验
    isPathExist = fse.realpathSync.native(absoluteDepPath) === absoluteDepPath;
  } catch (err) {
    isPathExist = false;
  }
  if (!isPathExist) {
    log.printErrorExit('LOCAL_DEPENDENCY_IN_PARAMETER_FILE_DOES_NOT_EXIST', [], [[absoluteDepPath, pathParamsObj.ohPackageFilePath, dependencyName]]);
  }

  if (fse.statSync(absoluteDepPath).isDirectory()) {
    // 对ohPackageInfoGetter函数返回值判空处理
    if (!ohPackageInfoGetter(path.resolve(absoluteDepPath, CommonConst.OH_PACKAGE_JSON5))){
      log.printErrorExit(
        'UNABLE_TO_GET_VERSION_AND_NAME_IN_OH_PACKAGE_JSON5',
        [dependencyName, path.resolve(absoluteDepPath, CommonConst.OH_PACKAGE_JSON5)],
        [[pathParamsObj.parameterFilePath]],
      );
    }

    // 依赖的是本地模块目录，需要返回该模块的真实版本号，该模块的 oh-package.json5 中的 version 也有可能是参数化的
    const { version, name } = ohPackageInfoGetter(path.resolve(absoluteDepPath, CommonConst.OH_PACKAGE_JSON5));

    if (dependencyName !== name) {
      log.printErrorExit(
        'THERE_ARE_SOME_DEPENDENCY_NAMES_THAT_ARE_INCONSISTENT_WITH_THE_ACTUAL_PACKAGE_NAMES',
        [],
        [[dependencyName, pathParamsObj.ohPackageFilePath, name, name]],
      );
    }

    const paramMatchArray = version.match(ParameterizationConst.REGEX);
    if (paramMatchArray) {
      // 本地依赖的版本号也是参数化的，需要进一步解析，此时调用不再传入 ohPackageInfoGetter
      const { value } = matchValueWithParameterizationKey({
        keys: paramMatchArray[1].split('.'),
        parameterFileJson,
        pathParamsObj,
        dependencyName: 'version', // resolveJsonObjWithoutParam 函数中调用 match 函数解析参数化版本号时该参数传的就是 version，保持代码逻辑相同
      });
      return { value };
    } else {
      return { value: version };
    }
  }

  const depExtname = path.extname(absoluteDepPath).toLowerCase();
  if (depExtname === '.har' || depExtname === '.tgz') {
    // 依赖的是本地har/hsp，假如har/hsp在当前模块内部，则需要保持原有的相对路径打进har包中，假如在模块外部则放到har包中的根目录
    // 本地 hsp 产物依赖指 release 模式生成的 .tgz文件，不包括 .hsp 文件
    const absoluteCurModuleDir = path.dirname(pathParamsObj.ohPackageFilePath);

    const isInsideModule = isSubPath(absoluteDepPath, absoluteCurModuleDir);

    const replaceValue = isInsideModule
      ? `${CommonConst.LOCAL_DEPENDENCY_PREFIX}${path.relative(absoluteCurModuleDir, absoluteDepPath)}`
      : `${CommonConst.LOCAL_DEPENDENCY_PREFIX}${path.basename(absoluteDepPath)}`;

    return {
      value: replaceValue,
      localDepArr: [absoluteDepPath],
    };
  }

  // 在 parameterFile 中填的本地依赖既不是目录，也不是 har/tgz，直接报错
  log.printErrorExit('UNRECOGNIZED_ARCHIVE_FORMAT_IN_PARAMETER_FILE', [], [[absoluteDepPath, pathParamsObj.ohPackageFilePath, dependencyName]]);
  return undefined;
}
